﻿using System;
using System.Collections.Generic;
using System.Text;

namespace BizElements.TemplateEngine
{
    /// <summary>ID/name and segment body.</summary>
    public sealed class TemplateSegment
    {
        #region Properties.

        /// <summary>Gets segment ID. Must be unique within template.</summary>
        public string Id { get; internal set; }

        /// <summary>Gets whether the template should attempt to programmatically insert a break-point and debug the segment.</summary>
        public bool Debug { get; internal set; }

        /// <summary>Gets debug condition. If set, the segment will be debugged only if current input component has the name specified in this property.</summary>
        public string DebugIfInputName { get; internal set; }

        /// <summary>Gets segment body.</summary>
        public string Body { get; internal set; }

        #endregion

        #region Parse segment parts. Parsed parts are cached in private field.

        List<SegmentPart> parsedParts;

        /// <summary>Parses and caches literals and expressions.</summary>
        internal IEnumerable<SegmentPart> Parse()
        {
            if (this.parsedParts == null)
                this.parsedParts = SegmentPartParser.ParseSegmentParts(this.Body);

            return this.parsedParts;
        }        

        #endregion
    }    

    /// <summary>Segment part. Literal or expression.</summary>
    sealed class SegmentPart
    {
        #region CTor and New methods.

        private SegmentPart(string body, bool isExpression)
        {
            this.Body = body;
            this.IsExpression = isExpression;
        }

        public static SegmentPart NewLiteral(string body)
        {
            return new SegmentPart(body, /*not expression*/ false);
        }

        public static SegmentPart NewExpression(string body)
        {
            return new SegmentPart(body, /*is expression*/ true);
        }

        #endregion

        #region Properties.

        public string Body { get; private set; }
        public bool IsExpression { get; private set; }

        #endregion
    }

    static class SegmentPartParser
    {
        #region Parse.

        private struct ParserState
        {
            public bool IsAtExpressionStart;
            public bool IsAtExpressionEnd;
            public bool IsBuildingExpression;
        }

        public static List<SegmentPart> ParseSegmentParts(string segmentBody)
        {
            List<SegmentPart> parts = new List<SegmentPart>();
            StringBuilder currLiteral = null;
            StringBuilder currExpression = null;
            foreach (char token in segmentBody)
            {
                ParserState state = DetermineParserState(currExpression, token);
                if (state.IsAtExpressionStart)
                {
                    // Finish literal if it was being built.
                    bool wasBuildingLiteralBeforeCurrExpression = IsNotEmpty(currLiteral);
                    if (wasBuildingLiteralBeforeCurrExpression)
                    {
                        parts.Add(SegmentPart.NewLiteral(currLiteral.ToString()));
                        currLiteral = null;
                    }

                    // New expression.
                    currExpression = new StringBuilder();
                }
                else if (state.IsBuildingExpression)
                {
                    currExpression.Append(token);
                }
                else if (state.IsAtExpressionEnd)
                {
                    // Finish current expression.
                    parts.Add(SegmentPart.NewExpression(currExpression.ToString()));
                    currExpression = null;
                }
                else
                {
                    // Building literal.
                    if (currLiteral == null)
                        currLiteral = new StringBuilder();

                    currLiteral.Append(token);
                }
            }

            // Finish last literal.
            bool wasBuildingLiteral = IsNotEmpty(currLiteral);
            if (wasBuildingLiteral)
                parts.Add(SegmentPart.NewLiteral(currLiteral.ToString()));

            return parts;
        }

        private static ParserState DetermineParserState(StringBuilder currExpressionSource, char token)
        {
            ParserState state = new ParserState();
            bool isExpressionDelimiter = (token == Template.ExpressionDelimiter);
            if (isExpressionDelimiter)
            {
                if (currExpressionSource == null)
                    state.IsAtExpressionStart = true;
                else
                    state.IsAtExpressionEnd = true;
            }
            else
            {
                if (currExpressionSource != null)
                    state.IsBuildingExpression = true;
            }

            return state;
        }

        private static bool IsNotEmpty(StringBuilder str)
        {
            return (str != null) && (str.Length > 0);
        }

        #endregion
    }

    /// <summary>Indexed segment collection which belongs to a single Template.</summary>
    sealed class TemplateSegmentCollection
    {
        #region Fields and properties.

        Dictionary<string, int> indexedSegmentNames;
        List<TemplateSegment> segments;

        /// <summary>Gets all template segment, including root segment.</summary>
        public IEnumerable<TemplateSegment> Segments
        {
            get { return this.segments; }
        }

        /// <summary>Gets root segment.</summary>
        public TemplateSegment RootSegment { get; private set; }

        #endregion

        #region CTor.

        /// <summary>CTor.</summary>
        public TemplateSegmentCollection(List<TemplateSegment> segments)
        {
            this.segments = segments;
            this.RootSegment = segments[0];
            this.indexedSegmentNames = new Dictionary<string, int>();
            for (int i = 0; i < this.segments.Count; i++)
                this.indexedSegmentNames[this.segments[i].Id] = i;
        }

        #endregion

        #region Methods.

        /// <summary>Gets segment with the specified ID/name.</summary>
        public TemplateSegment GetSegment(string segmentId)
        {
            int? listIdx = ArrayUtil.TryGetValue(this.indexedSegmentNames, segmentId);
            return (listIdx.HasValue) ? this.segments[listIdx.Value] : null;
        }

        #endregion
    }
}